import { Animation, AnimationClip, Asset, AssetManager, director, resources, Scene, SpriteAtlas, SpriteFrame } from "cc";
import { eventDispatcher } from "../Constants";
import { EventType } from "../enums/EventType";

export class CocosUtils {

    public static loadAssetDir<T extends Asset>(path: string, type: new () => T, onProgress?: (progress: number) => void, bundle: AssetManager.Bundle = resources): Promise<T[]> {
        return new Promise((resolve, reject) => {
            bundle.loadDir<T>(path, type, (finished, total) => {
                onProgress && onProgress(finished / total);
            }, (error: Error, assets: T[]) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(assets);
                }
            });
        });
    }

    public static loadAsset<T extends Asset>(path: string, type: new () => T, bundle: AssetManager.Bundle = resources): Promise<T> {
        return new Promise((resolve, reject) => {
            bundle.load<T>(path, type, (error: Error, asset: T) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(asset);
                }
            });
        });
    }

    public static getAsset<T extends Asset>(path: string, type: new () => T, bundle: AssetManager.Bundle = resources): T {
        return bundle.get<T>(path, type);
    }

    public static createAniamtionClip(path: string, wrapMode: AnimationClip.WrapMode = AnimationClip.WrapMode.Loop, sample: number = 8): AnimationClip {
        const spriteAtlas = this.getAsset(path, SpriteAtlas);
        const spriteFrames = this.sortSpriteFrame(spriteAtlas.getSpriteFrames());
        const clip = AnimationClip.createWithSpriteFrames(spriteFrames, sample);
        clip.wrapMode = wrapMode;
        return clip;
    }

    public static loadScene(sceneName: string): Promise<void> {
        return new Promise((resolve, reject) => {
            eventDispatcher.emit(EventType.LOAD_SCENE_START);
            director.loadScene(sceneName, (error: Error, scene: Scene) => {
                if (error) {
                    reject(error);
                } else {
                    eventDispatcher.emit(EventType.LOAD_SCENE_FINISH);
                    resolve();
                }
            });
        });
    }

    public static playAnimation(animation: Animation, animName: string, atlasPath: string, options?: { sample?: number, wrapMode?: AnimationClip.WrapMode, events?: AnimationClip.IEvent[] }): Promise<void> {
        return new Promise((resolve) => {
            if (!animation.getState(animName)) {
                const sample = options?.sample || 8;
                const wrapMode = options?.wrapMode || AnimationClip.WrapMode.Normal;
                const events = options?.events || [];
                const clip = this.createAniamtionClip(atlasPath, wrapMode, sample);
                clip.events.push(...events);
                clip.updateEventDatas();
                animation.createState(clip, animName);
            }
            animation.once(Animation.EventType.FINISHED, () => {
                eventDispatcher.emit(EventType.ANIMATION_FINISH);
                resolve();
            });
            eventDispatcher.emit(EventType.ANIMATION_START);
            animation.play(animName);
        });
    }

    public static sortSpriteFrame(spriteFrames: Array<SpriteFrame>): Array<SpriteFrame> {
        spriteFrames.sort((a, b) => this.getNumberWithinString(a.name) - this.getNumberWithinString(b.name));
        return spriteFrames;
    }

    public static getNumberWithinString(str: string): number {
        return parseInt(str.match(/\((\d+)\)/)?.[1] || '0');
    }
}